package com.novel.framework.web;

import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
import com.novel.common.exception.DemoModeException;
import com.novel.common.exception.business.BusinessException;
import com.novel.framework.result.Result;
import com.novel.captcha.exception.*;
import lombok.Builder;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.sql.SQLException;
import java.sql.SQLIntegrityConstraintViolationException;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;

/**
 * 异常统一处理
 *
 * @author novel
 * @since 2020/3/2
 */
@Slf4j
@RestControllerAdvice
public class ExceptionController {


    /**
     * 权限码异常
     *
     * @param e       异常
     * @param request 请求
     * @return 结果
     */
    @ExceptionHandler(NotPermissionException.class)
    public Result handleNotPermissionException(NotPermissionException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',权限码校验失败'{}'", requestURI, e.getMessage());
        return new Result(HttpStatus.UNAUTHORIZED.value(), false, "没有访问权限，请联系管理员授权", null);
    }

    /**
     * 角色权限异常
     *
     * @param e       异常
     * @param request 请求
     * @return 结果
     */
    @ExceptionHandler(NotRoleException.class)
    public Result handleNotRoleException(NotRoleException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',角色权限校验失败'{}'", requestURI, e.getMessage());
        return new Result(HttpStatus.UNAUTHORIZED.value(), false, "没有访问权限，请联系管理员授权", null);
    }

    /**
     * 认证失败
     *
     * @param e       异常
     * @param request 请求
     * @return 结果
     */
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    @ExceptionHandler(NotLoginException.class)
    public Result handleNotLoginException(NotLoginException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',认证失败'{}',无法访问系统资源", requestURI, e.getMessage());
        return new Result(HttpStatus.UNAUTHORIZED.value(), false, "认证失败，无法访问系统资源", null);
    }

    /**
     * 业务异常
     *
     * @param e 业务异常
     * @return 返回消息
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(BusinessException.class)
    public Result businessException(BusinessException e) {
        int code = 500;
        if (e.getCode() != null) {
            try {
                code = Integer.parseInt(e.getCode());
            } catch (Exception ignored) {
            }
        }
        log.error("{},{}", code, "业务异常：" + e.getMessage());
        return new Result(code, false, e.getMessage(), null);
    }

    /**
     * 数据库异常
     *
     * @param e 数据库异常
     * @return 返回消息
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(SQLException.class)
    public Result sqlException(SQLException e) {
        e.printStackTrace();
        log.error("{},{}", "500", "数据库异常：" + e.getMessage());
        return new Result(500, false, "数据库异常！", null);
    }

    /**
     * 数据库异常
     *
     * @param e 数据库异常
     * @return 返回消息
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(SQLIntegrityConstraintViolationException.class)
    public Result sqlIntegrityConstraintViolationException(SQLIntegrityConstraintViolationException e) {
        e.printStackTrace();
        log.error("{},{}", "500", "数据库异常：" + e.getMessage());
        return new Result(500, false, "数据库异常", null);
    }

    /**
     * 数据库异常
     *
     * @param e 数据库异常
     * @return 返回消息
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(DuplicateKeyException.class)
    public Result duplicateKeyException(DuplicateKeyException e) {
        e.printStackTrace();
        log.error("{},{}", "500", "数据库异常，主键重复：" + e.getMessage());
        return new Result(500, false, "主键重复", null);
    }

    /**
     * 验证码异常
     *
     * @param e 验证码异常
     * @return 结果
     */
    @ResponseStatus(HttpStatus.VARIANT_ALSO_NEGOTIATES)
    @ExceptionHandler(CaptchaException.class)
    public Result kaptchaException(CaptchaException e) {
        log.error("{},{}", "500", "验证码异常：" + e.getMessage());
        if (e instanceof CaptchaCacheException) {
            return new Result(500, false, "验证码服务异常，请联系管理员", null);
        } else if (e instanceof CaptchaIncorrectException) {
            return new Result(500, false, "验证码输入错误", null);
        } else if (e instanceof CaptchaIsEmptyException) {
            return new Result(500, false, "验证码不能为空", null);
        } else if (e instanceof CaptchaNotFoundException) {
            return new Result(500, false, "验证码不存在", null);
        } else if (e instanceof CaptchaRenderException) {
            return new Result(500, false, "验证码生成错误，请联系管理员", null);
        } else if (e instanceof CaptchaTimeoutException) {
            return new Result(500, false, "验证码已过时", null);
        } else {
            return new Result(500, false, "验证码错误", null);
        }
    }

    /**
     * 演示模式异常
     *
     * @param e 演示模式异常
     * @return 结果
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(DemoModeException.class)
    public Result demoModeException(DemoModeException e) {
        if (e.getMsg() != null && !e.getMsg().isEmpty()) {
            return Result.error(e.getMsg());
        }
        return Result.error("演示模式，不允许操作");
    }

    /**
     * 数据绑定校验异常
     *
     * @param e 异常
     * @return 返回消息
     */
    @ExceptionHandler(BindException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Result bindException(BindException e) {
        log.error("{},{}", "400", "数据绑定校验异常：" + e.getMessage());
        return getResultView("参数错误", e);
    }

    /**
     * 请求方式错误
     *
     * @param e 异常
     * @return 返回消息
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public Result requestMethodErrorException(HttpRequestMethodNotSupportedException e) {
        log.error("{},{}", "500", "请求方式不正确：" + e.getMessage());
        return new Result(500, false, "请求方式不正确：" + e.getMessage(), null);
    }


    /**
     * 捕捉其他所有异常
     *
     * @param request request
     * @param e       异常
     * @return 返回消息
     */
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public Result globalException(HttpServletRequest request, Exception e) {
        e.printStackTrace();
        log.error("{},{}", "500", "未知异常：" + e.getMessage());
        return new Result(getStatus(request).value(), false, e.getMessage(), null);
    }

    private HttpStatus getStatus(HttpServletRequest request) {
        Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
        if (statusCode == null) {
            return HttpStatus.INTERNAL_SERVER_ERROR;
        }
        return HttpStatus.valueOf(statusCode);
    }


    /**
     * 参数异常
     *
     * @param msg 异常信息
     * @param e   异常
     * @return 返回消息
     */
    private Result getResultView(String msg, BindException e) {
        BindingResult bindingResult = e.getBindingResult();
        List<ObjectError> allErrors = bindingResult.getAllErrors();
        Set<BindingResultObject> errorMessage = new HashSet<>();
        allErrors.forEach(item -> errorMessage.add(BindingResultObject.builder().build().setMessage(item.getDefaultMessage()).setField(((DefaultMessageSourceResolvable) Objects.requireNonNull(item.getArguments())[0]).getDefaultMessage())));
        return Result.builder().msg(msg).code(400).data(errorMessage).build();
    }
}

@Builder
@Data
class BindingResultObject implements Serializable {
    private String field;
    private String message;

    public BindingResultObject setField(String field) {
        this.field = field;
        return this;
    }

    public BindingResultObject setMessage(String message) {
        this.message = message;
        return this;
    }
}